﻿namespace Fiddler
{
    using System;
    using System.IO;
    using System.Text;

    public class WebSocketMessage
    {
        private byte[] _arrMask;
        private byte[] _arrRawPayload;
        private bool _bAborted;
        private bool _bBreakpoint;
        private bool _bIsFinalFragment;
        private bool _bOutbound;
        private byte _byteReservedFlags;
        private int _iID;
        private WebSocketFrameTypes _wsftType;
        private WebSocket _wsOwner;
        private DateTime dtBeginSend;
        private DateTime dtDoneRead;
        private DateTime dtDoneSend;
        private DateTime dtStartRead;

        internal WebSocketMessage(WebSocket oWSOwner, int iID, bool bIsOutbound)
        {
            this._wsOwner = oWSOwner;
            this._iID = iID;
            this._bOutbound = bIsOutbound;
        }

        private void _SetPayloadWithoutCopy(byte[] arrNewPayload)
        {
            MaskDataInPlace(arrNewPayload, this._arrMask);
            this._arrRawPayload = arrNewPayload;
        }

        public void Abort()
        {
            this._bAborted = true;
        }

        internal void AssignHeader(byte byteHeader)
        {
            this._bIsFinalFragment = 0x80 == (byteHeader & 0x80);
            this._byteReservedFlags = (byte) ((byteHeader & 0x70) >> 4);
            this.FrameType = (WebSocketFrameTypes) ((byte) (byteHeader & 15));
        }

        private static void MaskDataInPlace(byte[] arrInOut, byte[] arrKey)
        {
            if (arrKey != null)
            {
                for (int i = 0; i < arrInOut.Length; i++)
                {
                    arrInOut[i] = (byte) (arrInOut[i] ^ arrKey[i % 4]);
                }
            }
        }

        public byte[] PayloadAsBytes()
        {
            if (this._arrRawPayload == null)
            {
                return new byte[0];
            }
            byte[] arrOut = new byte[this._arrRawPayload.Length];
            if (this._arrMask != null)
            {
                UnmaskData(this._arrRawPayload, this._arrMask, arrOut);
                return arrOut;
            }
            Buffer.BlockCopy(this._arrRawPayload, 0, arrOut, 0, arrOut.Length);
            return arrOut;
        }

        public string PayloadAsString()
        {
            byte[] buffer;
            if (this._arrRawPayload == null)
            {
                return "<NoPayload>";
            }
            if (this._arrMask != null)
            {
                buffer = new byte[this._arrRawPayload.Length];
                UnmaskData(this._arrRawPayload, this._arrMask, buffer);
            }
            else
            {
                buffer = this._arrRawPayload;
            }
            if (this._wsftType == WebSocketFrameTypes.Text)
            {
                return Encoding.UTF8.GetString(buffer);
            }
            return BitConverter.ToString(buffer);
        }

        public void SetPayload(byte[] arrNewPayload)
        {
            byte[] dst = new byte[arrNewPayload.Length];
            Buffer.BlockCopy(arrNewPayload, 0, dst, 0, arrNewPayload.Length);
            this._SetPayloadWithoutCopy(dst);
        }

        public void SetPayload(string sPayload)
        {
            this._SetPayloadWithoutCopy(Encoding.UTF8.GetBytes(sPayload));
        }

        public byte[] ToByteArray()
        {
            byte[] buffer;
            if (this._arrRawPayload == null)
            {
                return new byte[0];
            }
            MemoryStream stream = new MemoryStream();
            stream.WriteByte((byte) (((this._bIsFinalFragment ? ((byte) 0x80) : ((byte) 0)) | ((byte) (this._byteReservedFlags << 4))) | ((int) this.FrameType)));
            ulong length = (ulong) this._arrRawPayload.Length;
            if (this._arrRawPayload.Length < 0x7e)
            {
                buffer = new byte[] { (byte) this._arrRawPayload.Length };
            }
            else if (this._arrRawPayload.Length < 0xffff)
            {
                buffer = new byte[] { 0x7e, (byte) (length >> 8), (byte) (length & 0xff) };
            }
            else
            {
                buffer = new byte[] { 0x7f, (byte) (length >> 0x38), (byte) ((length & 0xff000000000000L) >> 0x30), (byte) ((length & 0xff0000000000L) >> 40), (byte) ((length & 0xff00000000L) >> 0x20), (byte) ((length & 0xff000000L) >> 0x18), (byte) ((length & 0xff0000) >> 0x10), (byte) ((length & 0xff00) >> 8), (byte) (length & 0xff) };
            }
            if (this._arrMask != null)
            {
                buffer[0] = (byte) (buffer[0] | 0x80);
            }
            stream.Write(buffer, 0, buffer.Length);
            if (this._arrMask != null)
            {
                stream.Write(this._arrMask, 0, 4);
            }
            stream.Write(this._arrRawPayload, 0, this._arrRawPayload.Length);
            return stream.ToArray();
        }

        public override string ToString()
        {
            return string.Format("WS{0}\nMessageID:\t{1}.{2}\nMessageType:\t{3}\nPayloadString:\t{4}\nMasking:\t{5}\n", new object[] { this._wsOwner.ToString(), this._bOutbound ? "Client" : "Server", this._iID, this._wsftType, this.PayloadAsString(), (this._arrMask == null) ? "<none>" : BitConverter.ToString(this._arrMask) });
        }

        private static void UnmaskData(byte[] arrIn, byte[] arrKey, byte[] arrOut)
        {
            for (int i = 0; i < arrIn.Length; i++)
            {
                arrOut[i] = (byte) (arrIn[i] ^ arrKey[i % 4]);
            }
        }

        public WebSocketFrameTypes FrameType
        {
            get
            {
                return this._wsftType;
            }
            internal set
            {
                this._wsftType = value;
            }
        }

        public int iCloseReason
        {
            get
            {
                if (((this.FrameType != WebSocketFrameTypes.Close) || (this._arrRawPayload == null)) || (this._arrRawPayload.Length < 2))
                {
                    return -1;
                }
                byte[] dst = new byte[2];
                Buffer.BlockCopy(this._arrRawPayload, 0, dst, 0, 2);
                UnmaskData(dst, this._arrMask, dst);
                return ((dst[0] << 8) + dst[1]);
            }
        }

        public int ID
        {
            get
            {
                return this._iID;
            }
        }

        public bool IsOutbound
        {
            get
            {
                return this._bOutbound;
            }
        }

        public byte[] MaskingKey
        {
            get
            {
                return this._arrMask;
            }
            internal set
            {
                this._arrMask = value;
            }
        }

        public byte[] PayloadData
        {
            get
            {
                return this._arrRawPayload;
            }
            internal set
            {
                this._arrRawPayload = value;
            }
        }

        public bool WasAborted
        {
            get
            {
                return this._bAborted;
            }
        }
    }
}

